DOM
DOM:文档对象模型(Document Object Model,简称DOM),在前面的课程中我们目前已经学完了HTML、CSS、Javascrpt的基础内容,细心的同学也许会发现在前面的课程中,我们并没有利用Javascrpt和页面进行交互,这是因为我们还没有学习DOM,DOM是JAVASCRIPT操作HTML文档的接口,可以看做连接HTML、CSS的桥梁。
一、DOM的特点
1.DOM最大的特点就是将文档表示为节点树
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>网站标题</h1>
<div>
<h2>网站副标题</h2>
<img src="logo.png" alt="网站LOGO">
<div class="box">
网站内容
</div>
</div>
</body>
</html>
2.节点属性:nodeType(节点类型)
nodeType值 | 节点类型 |
---|---|
1 | 元素节点,例如 |
3 | 文字节点 |
8 | 注释节点 |
9 | document节点 |
10 | 文档类型声明:DTD节点 |
二、节点访问
1.认识document对象
- document对象是DOM中最重要的东西,几乎所有DOM的功能都封装在了document对象中
- document对象也表示整个HTML文档,它是DOM节点树的根
- document对象的nodeType属性值是9
2.认识document对象方法
- 通过ID获得元素:
document.getElementById()
- 从IE6开始支持
- 如果页面上有相同id的元素,则只能得到第一个
- 不管元素藏的位置有多深,都能通过id把它找到
<div id="div">div</div>
var div = document.getElementById('div')
- 通过标签名获得元素数组:
document.getElementsByTagName()
- 从IE6开始支持
- 即使页面上只有一个指定标签名的节点,也将得到长度为1的数组
- 任何一个节点元素也可以调用getElementsByTagName()方法,从而得到其内部的某种类的元素节点
<p>段落1</p>
<p>段落2</p>
<p>段落3</p>
<p>段落4</p>
<p>段落5</p>
<p>段落6</p>
<p>段落7</p>
<p>段落8</p>
var div = document.getElementsByTagName('p')
- 通过类名得到元素数组:
document.getElementsByClassName()
- 从IE9开始支持
<p class="p1">段落1</p>
<p class="p2">段落2</p>
<p class="p1">段落3</p>
<p class="p3">段落4</p>
<p class="p1">段落5</p>
<p>段落6</p>
<p>段落7</p>
<p>段落8</p>
var div = document.getElementsByTagName('p1')
- 通过选择器得到元素:
document.querySelector()
- IE8部分兼容、 IE9完全兼容
- querySelector()方法只能得到页面上的一个元素,如果有多个元素符合条件,则只能得到第一个元素
- querySelector()方法从IE8开始兼容,但从IE9开始支持 CSS3的选择器,如:
nth-child()
、:[src^='dog']
等CSS3选 择器形式都支持良好
<div id="div">
<p class="p">段落</p>
</div>
var div = document.querySelector('#div .p')
- 通过选择器得到元素数组:
document.querySelectorAll()
- 同
document.querySelector()
- 同
3.注意事项
- 在测试DOM代码时,通常JS代码一定要写到HTML节点的后面,否则JS无法找到相应HTML节点
- 如果在前面可以通过
window.onload = function(){}
实现
三、节点关系
注意事项:
- DOM中,文本也属于节点
- 在标准的W3C规范中,空白文本也算作节点,但是在IE8及以前的浏览器中,空白文本不算做节点
查询节点时如何排除文本节点
关系 | 对应方法(考虑所有节点) | 对应方法(只考虑元素节点) |
---|---|---|
子节点 | childNodes | children |
父节点 | parentNode | parentNode |
第一个子节点 | firstChild | firstElementChild |
最后一个子节点 | lastChild | lastElementChild |
前一个兄弟节点 | previousSibling | previousElementSibling |
后一个兄弟节点 | nextSibling | nextElementSibling |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>网站标题</h1>
<div id="div">
<h2>网站副标题</h2>
<img src="logo.png" alt="网站LOGO">
<div class="box">
网站内容
</div>
</div>
</body>
</html>
var div = document.getElementById('div')
console.log(div.childNodes)
console.log(div.children)
思考如何在IE8中查询所有的元素节点
四、节点操作
1.基础操作
- 改变元素节点中的内容
- innerHTML:以HTML语法设置节点中的内容
- innerText:以纯文本的形式设置节点中的内容
<div id="div">
网站内容
</div>
var div = document.getElementById('div')
div.innerText = '修改后内容'
- 获取/修改节点的HTML属性
- 通过打点的形式获取/修改
- 通过
getAttribute()
/setAttribute()
来获取、修改
<a id="link" href="https://www.baidu.com" title="百度搜索">百度搜索</a>
var div = document.getElementById('link')
console.log(div.href)
console.log(div.getAttribute('title'))
div.setAttribute('title', '新浪微博')
- 修改节点CSS样式
- css属性中有中横线的需要写成驼峰式
<div id="div">
网站内容
</div>
var div = document.getElementById('div')
div.style.backgroundColor = 'black'
div.style.fontSize = '32px'
2.进阶操作
2.1 节点创建:document.createElement()
- 新创建出的节点是“孤儿节点”,这意味着它并没有被挂载 到DOM树上,我们无法看见它
- 需要使用
appendChild()
或者insertBefore()
方法将孤儿节点插入到DOM树上
<div id="parent">
<div id="child"></div>
</div>
// 创建一个div元素
var div = document.createElement('div')
div.setAttribute('data-message', '新增节点')
// 将div元素插入到id为div的div中:父节点.appendChild(新建节点)
var parent = document.getElementById('parent')
parent.appendChild(div)
// 将div元素插入到id为child的div前面:父节点.insertBefore(孤儿节点, 标杆节点)
var child = document.getElementById('child')
parent.insertBefore(div, child)
2.2 节点移除:removeChild()
- 节点不能主动删除自己,必须由父节点删除它
<div id="parent">
<div id="child"></div>
</div>
var child = document.getElementById('child')
var parent = document.getElementById('parent')
parent.appendChild(child)
2.3 节点移动:节点创建中我们是将新增的“孤儿节点”插入DOM树中,我们看下能否将已存在于DOM树中的某个节点元素插入到其他位置,先看代码
<div id="parent">
<div id="child"></div>
</div>
<div id="other"></div>
var child = document.getElementById('child')
var other = document.getElementById('other')
other.appendChild(child)
从上面代码运行结果我们可以看出,当我们将已存在于DOM树中的某个节点插入到新的位置,该节点会从原来所在位置消失,即发生了节点移动,从而我们可以看出一个节点不能同时位于DOM树的两个位置
2.4 节点克隆:cloneNode(bool)
- 克隆节点,克隆出的节点是“孤儿节点”
- 参数是一个布尔值,表示是否采用深度克隆:如果为true,则该节点的所有后代节点也都会被克隆,如果为false,则只克隆该节点本身
<div id="parent">
<div id="child"></div>
</div>
var parent = document.getElementById('parent')
console.log(parent.cloneNode(false))
console.log(parent.cloneNode(true))
3.练习:动态创建出一个20行12列的表格
<table id="table"></table>
var table = document.getElementById('table');
for (var i = 0; i < 20; i++) {
// 创建了新的tr标签
var tr = document.createElement('tr');
for (var j = 0; j < 12; j++) {
// 创建了新的td标签
var td = document.createElement('td');
// 让tr追加td标签
tr.appendChild(td);
}
// 让mytable追加tr标签
table.appendChild(tr);
}
五、DOM事件
1.什么是事件
事件就是用户与网页的交互动作,比如点击某个按钮就是点击事件,在文本输入框中输入内容就是输入事件
2.事件监听
当网页执行某个事件的时候,能够告知程序员这个事件发生了,以便我们在开发时对该事件写一些实际操作,比如当我们点击了登录按钮的时候,对于计算机来说只是监听到登录按钮被点击了,但是点击后需要做什么操作计算机并不知道,这时候我们就需要通过登录按钮的点击事件监听来告诉计算机当登录按钮被点击后执行什么操作
3.如何设置事件监听
3.1 通过onxxx
的方式对事件进行监听
<button id="login">点击登录</button>
var btn = document.getElementById('login');
login.onclick = function () {
console.log('登录按钮被点击了')
}
下面是常见的监听事件:
事件名 | 事件描述 |
---|---|
onclick | 【鼠标事件】当鼠标单击某个对象 |
ondblclick | 【鼠标事件】当鼠标双击某个对象 |
onmousedown | 【鼠标事件】当某个鼠标按键在某个对象上被按下 |
onmouseup | 【鼠标事件】当某个鼠标按键在某个对象上被松开 |
onmousemove | 【鼠标事件】当某个鼠标按键在某个对象上被移动 |
onmouseenter | 【鼠标事件】当鼠标进入某个对象(相似事件onmouseover) |
onmouseleave | 【鼠标事件】当鼠标离开某个对象(相似事件onmouseout) |
onkeypress | 【键盘事件】当某个键盘的键被按下(系统按钮如箭头键和功能键无法得到识别) |
onkeydown | 【键盘事件】当某个键盘的键被按下(系统按钮可以识别,并且会先于onkeypress发生) |
onkeyup | 【键盘事件】当某个键盘的键被松开 |
onchange | 【表单事件】当用户改变域的内容 |
onfocus | 【表单事件】当某元素获得焦点 |
onblur | 【表单事件】当某元素失去焦点 |
onsubmit | 【表单事件】当表单被提交 |
onreset | 【表单事件】当表单被重置 |
onload | 【页面事件】当页面或图像被完成加载 |
onunload | 【页面事件】当用户退出页面 |
3.2 通过addEventLister
的方式对事件进行监听
<button id="login">点击登录</button>
var btn = document.getElementById('login');
login.addEventLister('click', function () {
console.log('登录按钮被点击了')
})
4.事件传播
事件的传播是:先从外到内,然后再从内到外
先看一段代码,来验证下这个结论
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
#box1{
width: 202px;
height: 202px;
border: 1px solid #000;
padding: 50px;
}
#box2{
width: 100px;
height: 100px;
border: 1px solid #000;
padding: 50px;
}
#box3{
width: 100px;
height: 100px;
border: 1px solid #000;
}
var oBox1 = document.getElementById('box1');
var oBox2 = document.getElementById('box2');
var oBox3 = document.getElementById('box3');
oBox2.onclick = function () {
console.log('我是box2的onclick');
};
oBox3.onclick = function () {
console.log('我是box3的onclick');
};
oBox1.onclick = function () {
console.log('我是box1的onclick');
};
通过上面代码的运行我们发现事件是的执行顺序是从内到外的,打脸了?为什么实际操作和定义结论不符合呢?这里涉及到一个知识点,事件的传播分为两个阶段:捕获阶段(从外到里)、冒泡阶段(从里到外)
- onxxx的方式只能监听冒泡阶段,属于DOM0级别事件监听
- 想要监听捕获阶段需要使用addEventLister,DOM2级别事件监听
// addEventLister有三个参数,分别是监听事件、监听到事件后执行操作即事件处理函数、监听阶段,当监听阶段为true的时候可以监听捕获阶段,为false时监听冒泡阶段
var oBox1 = document.getElementById('box1');
var oBox2 = document.getElementById('box2');
var oBox3 = document.getElementById('box3');
oBox2.addEventListener('click', function() {
console.log('我是box2的冒泡阶段');
}, false);
oBox3.addEventListener('click', function() {
console.log('我是box3的捕获阶段');
}, true);
oBox3.addEventListener('click', function() {
console.log('我是box3的冒泡阶段');
}, false);
oBox3.onclick = function () {
console.log('我是box3的onclick');
};
oBox1.addEventListener('click', function() {
console.log('我是box1的冒泡阶段');
}, false);
oBox2.addEventListener('click', function() {
console.log('我是box2的捕获阶段');
}, true);
oBox1.addEventListener('click', function() {
console.log('我是box1的捕获阶段');
}, true);
oBox1.onclick = function () {
console.log('我是box1的onclick');
};
oBox2.onclick = function () {
console.log('我是box2的onclick');
};
【总结】
- 最内部的元素不区分捕获和冒泡,会现实性卸载前面的监听,然后执行后面的监听事件
- 如果给同一个元素设置相同的两个同名事件,DOM0级写法后面的会覆盖前面的,DOM2级会按顺序执行
【思考捕获和冒泡的使用场景】
5.事件对象
事件处理函数提供一个形式参数,它是一个对象,封装了本次事件的细节,这个参数通常用单词event或字母e来表示
小练习:通过键盘方向键实现盒子的移动
<div id="box"></div>
#box {
position: absolute;
top: 200px;
left: 200px;
width: 100px;
height: 100px;
background-color: orange;
}
var oBox = document.getElementById('box');
// 全局变量t、l,分别表示盒子的top属性值和left属性值
var t = 200;
var l = 200;
// 监听document对象的键盘按下事件监听,表示当用户在整个网页上按下按键的时候
document.onkeydown = function (e) {
switch (e.keyCode) {
case 37:
l -= 3;
break;
case 38:
t -= 3;
break;
case 39:
l += 3;
break;
case 40:
t += 3;
break;
}
// 更改样式
oBox.style.left = l + 'px';
oBox.style.top = t + 'px';
};
事件对象方法
e.preventDefault()
:用来阻止事件产生的“默认动作”,例子:制作一个文本框,只能让用户在其中输入小写字母和数字,其他字符输入没有效果
<div>
<label>只能输入小写字母和数字:</label>
<input type="text" id="field">
</div>
var oField = document.getElementById('field');
oField.onkeypress = function (e) {
console.log(e.charCode);
// 根据用户输入的字符的字符码(e.charCode)
// 数字0~9,字符码48~57
// 小写字母a~z,字符码97~122
if (!(e.charCode >= 48 && e.charCode <= 57 || e.charCode >= 97 && e.charCode <= 122)) {
// 阻止浏览器的默认行为
e.preventDefault();
}
};
e.stopPropagation()
:用来阻止事件继续传播,例子:制作一个弹出层:点击按钮显示弹出层,点击网页任意地方, 弹出层关闭
<button id="btn">按我弹出弹出层</button>
<div class="modal" id="modal"></div>
.modal {
width: 400px;
height: 140px;
background-color: #333;
position: absolute;
top: 50%;
left: 50%;
margin-top: -70px;
margin-left: -200px;
display: none;
}
var oBtn = document.getElementById('btn');
var oModal = document.getElementById('modal');
// 点击按钮的时候,弹出层显示
oBtn.onclick = function (e) {
// 阻止事件继续传播到document身上
e.stopPropagation();
oModal.style.display = 'block';
};
// 点击页面任何部分的时候,弹出层关闭
document.onclick = function () {
oModal.style.display = 'none';
};
// 点击弹出层内部的时候,不能关闭弹出层的,所以应该阻止事件继续传播
oModal.onclick = function (e) {
// 阻止事件继续传播到document身上
e.stopPropagation();
};
六、事件委托
先来看一段代码:
<ul id="list">
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
</ul>
var oList = document.getElementById('list');
var lis = oList.getElementsByTagName('li');
// 书写循环语句,批量给元素添加监听
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
// 在这个函数中,this表示点击的这个元素,this涉及函数上下文的相关知识,我们在“面向对象”课程中介绍
this.style.color = 'red';
};
}
每一个事件监听注册都会消耗一定的系统内存,而批量添加事件会导致监听数量太多,内存消耗会非常大,如何优化这个问题呢?
利用事件冒泡机制,将后代元素事件委托给祖先元素,这就是事件委托,这里涉及到两个属性
- e.target:触发此事件最早的元素,即事件”事件源元素“
- e.currentTarget:事件处理程序附加到的元素
<ul id="list">
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
</ul>
var oList = document.getElementById('list');
var oBtn = document.getElementById('btn');
oList.onclick = function (e) {
// e.target表示用户真正点击的那个元素
e.target.style.color = 'red';
};
适用场景
- 当有大量类似元素需要批量添加事件监听时,使用事件委 托可以减少内存开销
- 当有动态元素节点上树时,使用事件委托可以让新上树的元素具有事件监听
注意事项
- 最内层元素不能再有额外的内层元素了
- 使用事件委托时要注意:不能委托不冒泡的事件给祖先元素(onmouseenter)